﻿#include "OiPCH.hpp"

#include "View/OiScene.hpp"
#include "Ctrl/OiAxis.hpp"

namespace Oi
{
    namespace PLScannerCore
    {
        class DiffViewItem : public QGraphicsItem
        {
        public:
            DiffViewItem(QGraphicsItem *parent = nullptr) :QGraphicsItem(parent)
            { }

            QRectF boundingRect() const;

            void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR);

        public:
            QList<QList<QPointF>> _profiles;

        };

        QRectF DiffViewItem::boundingRect() const
        {
            QRectF rect;

            for (auto profile : _profiles)
            {
                auto pR = QPolygonF(profile.toVector()).boundingRect();
                if (rect.isEmpty())
                {
                    rect = pR;
                }
                else
                {
                    rect |= pR;
                }
            }

            return rect.normalized().adjusted(-0.1, -0.1, 0.1, 0.1);
        }

        void lineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget /*= Q_NULLPTR*/)
        {
            auto transform = painter->transform();

            painter->save();
            //painter->translate(QPointF(boundingRect().topLeft().x(), -boundingRect().topLeft().y()));
            painter->scale(1.0 / transform.m11(), 1.0 / transform.m22());
            painter->setPen(QPen(PLScannerStyle::ChartLineColor(), 1));

            for(auto profile : _profiles)
            {
                for (auto& p : profile)
                {
                    p.setX(p.x() * transform.m11());
                    p.setY(p.y() * transform.m22());
                }
                painter->drawPolyline(profile.toVector().data(), profile.size());
            }

            painter->restore();
        }

        class LineViewImpl : public Impl<LineView>, public QObject
        {
        public:
            void init();
            void exit();

        public:
            bool preview(const QList<QList<QPointF>>& profiles);
            bool preview(const QList<QPointF>& profile);

        public:
            void fitInView(const QGraphicsItem *item, Qt::AspectRatioMode aspectRatioMode);

        public:
            void relayout(int w = -1, int h = -1);
            void updateAxis();

        public:
            int _mode;

            lineItem* _line;

            Ctrl::Axis* _hAxis;
            QGraphicsProxyWidget* _hAxisProxy;

            Ctrl::Axis* _vAxis;
            QGraphicsProxyWidget* _vAxisProxy;

            View::Scene* _scene;

            Qt::Orientation _orientation;

            QList<QList<QPointF>> _profiles;

            QList<QPointF> _profile;
        };

        void LineViewImpl::init()
        {
            OI_Q(LineView);

            _mode = 0;

            q->setRenderHints(q->renderHints() | QPainter::Antialiasing);
            q->setDragMode(QGraphicsView::RubberBandDrag);
            q->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
            q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
            q->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
            q->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
            q->setMouseTracking(true);
            q->setFrameShape(QFrame::NoFrame);
            q->setBackgroundBrush(PLScannerStyle::ChartBgColor());
            q->setAttribute(Qt::WA_MacNormalSize);

            _scene = new View::Scene(q);

            q->setScene(_scene);

            _vAxis = new Ctrl::Axis();
            _vAxis->setMode(Ctrl::Axis::vertical);
            _vAxis->setType(Ctrl::Axis::rangeType);
            _vAxis->setAttribute(Qt::WA_GroupLeader);
            _vAxisProxy = _scene->addWidget(_vAxis);
            _vAxisProxy->setFlag(QGraphicsItem::ItemIsFocusable, false);
            _vAxisProxy->setZValue(300);
            _vAxis->setStyleSheet("font-size:6pt;background-color:#a7a7a7");

            _hAxis = new Ctrl::Axis();
            _hAxis->setMode(Ctrl::Axis::horiztal);
            _hAxis->setType(Ctrl::Axis::rangeType);
            _hAxis->setAttribute(Qt::WA_GroupLeader);
            _hAxisProxy = _scene->addWidget(_hAxis);
            _hAxisProxy->setFlag(QGraphicsItem::ItemIsFocusable, false);
            _hAxisProxy->setZValue(300);
            _hAxis->setStyleSheet("font-size:6pt;background-color:#a7a7a7");

            _line = new lineItem();
            _scene->addItem(_line);
            _line->setZValue(1);

            _scene->setSceneRect(1.0 * INT_MIN, 1.0 * INT_MIN, 2.0 * INT_MAX, 2.0 * INT_MAX);

            auto transform = q->transform();
            transform.rotate(180.0, Qt::XAxis);
            q->setTransform(transform);

            q->setCursor(Qt::ArrowCursor);
        }

        void LineViewImpl::exit()
        {
            if (_line)
            {
                delete _line;
            }

            if(_scene)
            {
                delete _scene;
            }
        }

        void LineViewImpl::relayout(int w, int h)
        {
            OI_Q(LineView);

            if(w == -1 || h == -1)
            {
                w = q->viewport()->width();
                h = q->viewport()->height();
            }

            _hAxis->resize(QSize(w, 30));
            _vAxis->resize(QSize(32, h));

            QPointF hPos = q->mapToScene(0, h - 30);
            QPointF vPos = q->mapToScene(0, 0);

            _hAxisProxy->setPos(hPos);
            _vAxisProxy->setPos(vPos);

            QTransform proxyTransform;
            proxyTransform.scale(1.0 / q->transform().m11(), 1.0 / q->transform().m22());

            _hAxisProxy->setTransform(proxyTransform);
            _vAxisProxy->setTransform(proxyTransform);

            updateAxis();
        }

        bool LineViewImpl::preview(const QList<QList<QPointF>>& profiles)
        {
            _profiles = profiles;
            _line->_profiles = profiles;

            if(!_line->_profiles.isEmpty())
            {
                if(OI_SELF()->testAttribute(Qt::WA_MacNormalSize))
                {
                    OI_SELF()->zoomFit();
                }
            }

            return true;
        }

        bool LineViewImpl::preview(const QList<QPointF>& profile)
        {
            _profile = profile;

            _line->_profiles.clear();
            _line->_profiles.push_back(profile);

            if(!_line->_profiles.isEmpty())
            {
                OI_SELF()->zoomFit();
            }

            return true;
        }

        void LineViewImpl::fitInView(const QGraphicsItem *item, Qt::AspectRatioMode aspectRatioMode)
        {
            OI_Q(LineView);

            // Reset the view scale to 1:1.
            QRectF unity = q->matrix().mapRect(QRectF(0, 0, 1, 1));

            if(unity.isEmpty())
            {
                return;
            }

            q->scale(1 / unity.width(), 1 / unity.height());

            int margin = 2;

            QRectF viewRect = q->viewport()->rect().adjusted(margin + (_vAxis->isVisible() ? _vAxis->width() : 0), margin, -margin, -margin - (_hAxis->isVisible() ? _hAxis->height() : 0));

            if(viewRect.isEmpty())
            {
                return;
            }

            QPainterPath path = item->isClipped() ? item->clipPath() : item->shape();

            QRectF rect = item->sceneTransform().map(path).boundingRect();
            QRectF sceneRect = q->matrix().mapRect(rect);
            if(sceneRect.isEmpty())
            {
                return;
            }

            qreal xratio = viewRect.width() / sceneRect.width();
            qreal yratio = viewRect.height() / sceneRect.height();

            auto offset = QPointF(0, 0);

            // Respect the aspect ratio mode.
            switch(aspectRatioMode)
            {
            case Qt::KeepAspectRatio:
            xratio = yratio = qMin(xratio, yratio);
            q->scale(xratio, yratio);
            offset.setX(-0.5 * (_vAxis->isVisible() ? _vAxis->width() : 0) / xratio);
            offset.setY(0.5 * (_hAxis->isVisible() ? _hAxis->height() : 0) / yratio);
            q->centerOn(rect.center() + offset);
            break;
            case Qt::KeepAspectRatioByExpanding:
            q->scale(xratio, xratio);
            q->centerOn(rect.center().x(), viewRect.center().y() / xratio);
            break;
            case Qt::IgnoreAspectRatio:
            q->scale(xratio, yratio);
            offset.setX(-0.5 * ( _vAxis->isVisible() ? _vAxis->width() : 0 ) / xratio);
            offset.setY(-0.5 * ( _hAxis->isVisible() ? _hAxis->height() : 0 ) / yratio);
            q->centerOn(rect.center() + offset);
            break;
            }

            relayout();
        }

        void LineViewImpl::updateAxis()
        {
            OI_Q(LineView);

            QPointF tl = q->mapToScene(0, 0);
            QPointF br = q->mapToScene(q->viewport()->width(), q->viewport()->height());

            double step = 0.1 * qFloor(2.0 * (tl.y() - br.y() ));
            if( step < 0.1) step = 0.1;
            _vAxis->setStep(step);
            _vAxis->setStart(br.y());
            _vAxis->setEnd(tl.y());
            _vAxis->setDecimals(1);
            _vAxis->update();

            step = qFloor(fabs(tl.x() - br.x()) / 10);
            if(step < 0.1) step = 0.1;
            _hAxis->setStep(step);
            _hAxis->setStart(tl.x());
            _hAxis->setEnd(br.x());
            _hAxis->setDecimals(0);
            _hAxis->update();

            emit q->axisHasChanged();
        }

        LineView::LineView(Qt::Orientation orientation/* = Qt::Horizontal*/, QWidget* parent) : QGraphicsView(parent)
        {
            OI_I(LineView)->init();
            d->_orientation = orientation;
        }

        LineView::~LineView()
        {
            OI_E(LineView)->exit();
        }

        QList<QPointF> LineView::profile() const
        {
            return OI_IMPL()->_profile;
        }

        QList<QList<QPointF>> LineView::profiles() const
        {
            return OI_IMPL()->_profiles;
        }

        void LineView::display(const QList<QList<QPointF>>& profiles)
        {
            OI_F(LineView);
            d->preview(profiles);
        }

        void LineView::display(const QList<QPointF>& profile)
        {
            OI_F(LineView);
            d->preview(profile);
        }

        void LineView::invalidate()
        {
            if(scene())
            {
                scene()->invalidate();
            }
        }

        void LineView::zoomOut(double factor)
        {
            OI_F(LineView);

            qreal factorX = transform().m11();
            qreal factorY = transform().m22();
            scale(1.0 / factorX, 1.0 / factorY);

            d->relayout();
        }

        void LineView::zoomFit()
        {
            OI_F(LineView);

            d->fitInView(d->_line, Qt::IgnoreAspectRatio);
        }

        void LineView::mousePressEvent(QMouseEvent *event)
        {
            OI_F(LineView);

            if(event->button() == Qt::MiddleButton || ( event->button() == Qt::LeftButton && ( event->modifiers() & Qt::CTRL ) ))
            {
                setDragMode(QGraphicsView::ScrollHandDrag);
                QMouseEvent evt(event->type(), event->pos(), Qt::LeftButton, event->buttons(), event->modifiers());
                QGraphicsView::mousePressEvent(&evt);
                event->accept();
                d->relayout();
                return;
            }
            QGraphicsView::mousePressEvent(event);
        }

        void LineView::mouseReleaseEvent(QMouseEvent *event)
        {
            OI_F(LineView);

            if(event->button() == Qt::MiddleButton || ( event->button() == Qt::LeftButton && ( event->modifiers() & Qt::CTRL ) ))
            {
                QMouseEvent evt(event->type(), event->pos(), Qt::LeftButton, event->buttons(), event->modifiers());
                QGraphicsView::mouseReleaseEvent(&evt);
                event->accept();
                setDragMode(QGraphicsView::RubberBandDrag);
                d->relayout();
                return;
            }

            QGraphicsView::mouseReleaseEvent(event);
        }

        void LineView::mouseMoveEvent(QMouseEvent *event)
        {
            OI_F(LineView);

            QGraphicsView::mouseMoveEvent(event);

            if(dragMode() == QGraphicsView::ScrollHandDrag)
            {
                d->relayout();
            }
        }

        void LineView::mouseDoubleClickEvent(QMouseEvent *event)
        {
            OI_F(LineView);

            QGraphicsView::mouseDoubleClickEvent(event);

            if(event->button() == Qt::MiddleButton)
            {
                zoomFit();
            }

        }

        void LineView::wheelEvent(QWheelEvent *event)
        {
            OI_F(LineView);
          
            if(event->delta() > 0)
            {
                if(transform().m11() < 1e+5)
                {
                    scale(1.0 * 1.15, 1.0 * 1.15);
                    d->relayout();
                }
            }
            else
            {
                if(transform().m11() > 1e-5)
                {
                    scale(1.0 / 1.15, 1.0 / 1.15);
                    d->relayout();
                }
            }
        }

        void LineView::resizeEvent(QResizeEvent *event)
        {
            OI_F(LineView);

            int w = viewport()->width();
            int h = viewport()->height();

            QGraphicsView::resizeEvent(event);

            d->relayout(w, h);

            zoomFit();
        }

        void LineView::showEvent(QShowEvent *event)
        {
            OI_F(LineView);
            QGraphicsView::showEvent(event);
            d->relayout();
        }

        void LineView::hideEvent(QHideEvent *event)
        {
            OI_F(LineView);
            QGraphicsView::hideEvent(event);
            d->relayout();
        }

        void LineView::paintEvent(QPaintEvent *event)
        {
            QGraphicsView::paintEvent(event);
        }
    }
}